package com.atguigu.utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

//数据库连接工具类
public class JdbcUtils {
    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns=new ThreadLocal<>();
    //终于想明白了,为什么ThreadLocal 类要是一个类似map的类型的存储结构.
    //而且为什么conns是静态的,为什么又不影响全局内任何线程来存储数值,影响其他线程?
    //首先说,static conns是静态成员变量,这种静态成员变量跟随类的加载而生成,类的加载区域叫方法区,类的静态成员变量保存的区域叫方法区静态域
    //static conns静态成员变量保存在方法区静态域中,常年不动位置,任何实例后的对象,只用该成员变量都要到方法区静态域中查找,这样就保证了全部实例对象拿到的成员变量是同一个.
    //好了,这里说明白了"静态成员变量"的存储部署,和唯一性,接下来解释"多个对象同时修改"静态成员变量,不会相互干扰吗?
    //正常情况下,是会干扰的.那是因为,正常情况下,静态成员变量,存的是固定值.如果存的是固定值,大家一起修改,肯定会相互干扰.
    //但是,这里"静态成员变量"存的是指针,它指向了堆,堆里有啥,有个对象,啥对象,ThreadLocal对象.
    //如果你每次都用实例对象去修改conns的指针,那么确实会干扰到其他对象的读.
    //但是这里不让你在对象中该conns的指针,因为类用了private权限.即"类外不允许重新赋值".
    //而且,这里每次让你操作的行为,只有两个conns.set(),conns.get()
    //也就是每次只让你改堆里的对象的内容.有人说了,这不是又成了一个孤立的变量,被反复修改了吗?
    //还真是.不过你注意conns 是ThreadLocal类的.这个ThreadLocal类是类似map那样,有key,有value的.
    //所以,你每次ThreadLocal.set(),都是修改某一项的value.那又有人说,是随机修改某一项吗?那当然不是了
    //每次ThreadLocal.set(),只能修改以自己线程名为key的那一项的value.
    //所以呀,把线程刚刚运行时创建的数据库连接id存入ThreadLocal对象的指定value上,以后本线程再使用mysql连接,就到ThreadLocal中拿属于自己线程的mysql连接
    //这就保证了同一个线程内的全部mysql操作使用的是同一个mysql连接.
    //所以,只需在程序中,找到已经全部mysql操作结束,本线程即将退出之际的位置,执行mysql事务提交,即可把mysql全部操作提交到数据库,同时关闭mysql连接,同时清空本线程在ThreadLocal中存储的键值对
    //但是也必须注意,在程序中第一次处理mysql操作后,ThreadLocal键值对已建立后,此后一直到全部mysql处理完,都要监听异常
    //一旦中间处理环节发生异常,底层BaseDao将无法执行,这时需要BaseDao将异常上报调用者,从而触发回滚操作
    //回滚操作,是反向清理sql,并删除ThreadLocal中保存的键值对
    //其实我对这里"异常发生在中间环节,异常发现于baseDao"中,不是很理解?为什么不能是任何后续处理都是向上抛出异常?那样不是更完整吗?
    //你现在是int i=12/0;导致的异常,那以后万一是在赋值图片时,发现图片没有呢,那么你是不是应该检测复制结果,然后抛出异常呢?
    //我要去试试!
    //我试过了,确实可以在中间环节就抛出异常,并被调用者捕获,从而触发回滚操作.不必非要在BaseDao中统一接收异常.
    // 在任何中间环节都可以抛出"throw new RuntimeException(throwables)"异常,并阻止程序进一步在中间环节执行,直接返回调用者中处理起异常中的回滚操作.

    static {
        try {
            Properties properties=new Properties();//读取文件得到一个流,分析得出各型参数
            InputStream inputStream=JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");//加载一个文件,得到一个流,动态代理
            properties.load(inputStream);//读取文件得到一个流,分析得出各型参数
            //创建连接池
            dataSource= (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
            //System.out.println(dataSource.getConnection());//打印获得到的连接
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {//main方法执行,类被加载,静态代码块被执行
        //对象实例化,非静态代码块被执行
    }//不测试的时候把这个主方法关闭
    /**
     * 获取数据库连接池中的连接
     * @return 如果返回null,说明获取连接失败,有值就是成功
     */
    public static Connection getConnection(){
        //Connection conn=null;
        //try {
        //    conn=dataSource.getConnection();
        //} catch (SQLException throwables) {
        //    throwables.printStackTrace();
        //}
        //return conn;
        //下面即将写的是关于事务的内容
        Connection conn=conns.get();
        if(conn==null){
            try {
                conn=dataSource.getConnection();//从数据库连接池中获取连接
                conns.set(conn);//保存到ThreadLocal对象中,供后续的jdbc操作使用
                //conn.setAutoCommit(true);//设置自动管理事务
                conn.setAutoCommit(false);//设置手动管理事务
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return conn;
    }

    /**
     * 提交事务,并关闭释放连接
     */
    public static void commitAndClose(){
        Connection connection = conns.get();
        if(connection !=null){//如果connection不等于空,那么就说明这个数据库连接被使用过
            try {
                connection.commit();//提交事务
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {//不管是否提交成功,都需要关闭数据库连接
                    connection.close();//关闭数据库连接,释放资源
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        conns.remove();
        //一定要执行conns.remove()操作,否则一定会报错.因为tomcat使用了线程池的技术 不是很懂?
    }

    /**
     * 回滚事务,并关闭连接
     */
    public static void rollbackAndClose(){
        Connection connection = conns.get();
        if(connection !=null){//如果connection不等于空,那么就说明这个数据库连接被使用过
            try {
                connection.rollback();//回滚事务
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {//不管是否提交成功,都需要关闭数据库连接
                    connection.close();//关闭数据库连接,释放资源
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        conns.remove();
        //一定要执行conns.remove()操作,否则一定会报错.因为tomcat使用了线程池的技术
    }
    /**
     * 关闭连接,放回数据库连接池
     * @param conn 连接体
     */
    //因为要使用事务,所以关闭行为发生在事务内部,所以这个关闭方法就被舍弃了
    //public static void close(Connection conn){
    //    if(conn!=null){
    //        try {
    //            conn.close();
    //        } catch (SQLException throwables) {
    //            throwables.printStackTrace();
    //        }
    //    }
    //}
}
